#![cfg(test)]

use std::rc::Rc;

use crate::{comptime::parse_exprs, parse, runtime::{env::EnvExt, Appliable, Env}, EvalResult, Expr};

use super::Value;

#[test]
fn run() -> crate::Result<()> {
    let global = Env::root();
    global.set_value("y", 32);

    let func = Expr::try_from("|x| (* x x)")?;

    let ans: i32 = global.eval_apply(func, parse_exprs("(* y 3)")?)?.try_into()?;
    println!("{ans:?}");

    Ok(())
}

#[test]
fn format_string() -> crate::Result<()> {
    let global = Env::root();
    global.set_macro("format", fmt);

    let m: String = global.eval(Expr::try_from(r#"(format "Hello," " my world")"#)?)?.try_into()?;
    println!("{}", m);

    let func = Expr::try_from(r#"|a b| (format "a = " a ", and b = " b)"#)?;

    let v = global.eval_apply(func, parse_exprs("233 456")?)?;
    println!("{v}");

    Ok(())
}

#[test]
fn highlevel_func() -> crate::Result<()> {
    let global = Env::root();
    let ret = global.eval("(((|a| || a) 1))".try_into()?)?;
    assert!(ret.is_integer(1));
    Ok(())
}

#[test]
fn cons() -> crate::Result<()> {
    let global = Env::root();
    let link = Expr::try_from("(cons 1 (cons 2 (cons 3 ())))")?;
    let ret = global.eval(link)?;
    println!("{ret}");
    Ok(())
}

#[test]
fn error() -> crate::Result<()> {
    let global = Env::root();
    let err = global.eval(crate::parse("(err 123)")?);

    if let Err(err) = err {
        println!("{err}");
    }

    Ok(())
}

#[test]
fn quote() -> crate::Result<()> {
    let global = Env::root();
    let ret = global.eval(crate::parse("(def a 3) (eval '(if #t a 2))")?)?;

    println!("{ret}");

    Ok(())
}

fn fmt(env: Rc<Env>, mut exprs: Vec<Expr>) -> EvalResult {
    let mut acc = if let Some(expr) = exprs.pop() {
        env.eval(expr)?.to_string()
    } else {
        return Ok(Value::Nil);
    };

    while let Some(expr) = exprs.pop() {
        acc.push_str(&env.eval(expr)?.to_string());
    }

    Ok(Value::String(acc))
}

fn builtin_println(env: Rc<Env>, mut exprs: Vec<Expr>) -> EvalResult {
    let mut acc = if let Some(expr) = exprs.pop() {
        env.eval(expr)?.to_string()
    } else {
        return Ok(Value::Nil);
    };

    while let Some(expr) = exprs.pop() {
        acc.push_str(&env.eval(expr)?.to_string());
    }

    println!("{acc}");
    Ok(Value::Nil)
}


#[test]
fn builtin_lambda() -> crate::Result<()> {
    struct Adder(i32);
    impl Appliable for Adder {
        fn apply(&mut self, _: Rc<Env>, _: Vec<Expr>) -> EvalResult {
            self.0 += 1;
            Ok(Value::Integer(self.0))
        }
    }

    let global = Env::root();
    global.set_macro("println", builtin_println);
    //global.set_lambda("adder", Rc::new(RefCell::new(Adder(0))));
    global.make_lambda("adder", Adder(0));

    global.eval(parse("(println (adder))(println (adder))(println (adder))")?)?;

    Ok(())
}